/****************************************************
 * 创建人：@author fengxin
 * 创建时间: 2019/12/21/15:32
 * 项目名称: SpringCodeGen-JavaFX
 * 文件名称: Controller.java
 * 文件描述: 生成spring代码的service
 *
 * All rights Reserved, Designed By 投资交易团队
 * @Copyright:2016-2019
 *
 ********************************************************/
package com.ysstech.codegen.controller;

import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.ysstech.codegen.entity.UserOperationInfo;
import com.ysstech.codegen.enumeration.DataBaseEnum;
import com.ysstech.codegen.enumeration.ModelTypeEnum;
import com.ysstech.codegen.service.SpringCodeGenService;
import com.ysstech.codegen.service.impl.SpringCodeGenServiceImpl;
import com.ysstech.codegen.util.DataBaseUtils;
import com.ysstech.codegen.util.FileUtils;
import com.ysstech.codegen.util.StringUtils;
import com.ysstech.codegen.util.UIComponentUtils;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import lombok.extern.slf4j.Slf4j;


/**
 * 包名称：com.ysstech.plugin.idea.codegen.util
 * 类名称：SpringCodeGenController
 * 类描述：controller
 * 创建人：@author fengxin
 * 创建时间：2019/12/12/10:49
 */


@Slf4j
public class SpringCodeGenController {


    private SpringCodeGenService springCodeGenService;

    private UserOperationInfo userOperationInfo = new UserOperationInfo();


    @FXML // ResourceBundle that was given to the FXMLLoader
    private ResourceBundle resources;

    @FXML // URL location of the FXML file that was given to the FXMLLoader
    private URL location;

    @FXML // fx:id="mainFlowPane"
    private FlowPane mainFlowPane; // Value injected by FXMLLoader

    @FXML // fx:id="topAnchorPane"
    private AnchorPane topAnchorPane; // Value injected by FXMLLoader

    @FXML // fx:id="allCheck"
    private CheckBox allCheck; // Value injected by FXMLLoader

    @FXML // fx:id="entityCheck"
    private CheckBox entityCheck; // Value injected by FXMLLoader

    @FXML // fx:id="reqVoCheck"
    private CheckBox reqVoCheck; // Value injected by FXMLLoader

    @FXML // fx:id="repVoCheck"
    private CheckBox repVoCheck; // Value injected by FXMLLoader

    @FXML // fx:id="mapperCheck"
    private CheckBox mapperCheck; // Value injected by FXMLLoader

    @FXML // fx:id="serviceCheck"
    private CheckBox serviceCheck; // Value injected by FXMLLoader

    @FXML // fx:id="controllerCheck"
    private CheckBox controllerCheck; // Value injected by FXMLLoader

//    @FXML // fx:id="frontendCheck"
//    private CheckBox frontendCheck; // Value injected by FXMLLoader

    @FXML // fx:id="middleAnchorPane"
    private AnchorPane middleAnchorPane; // Value injected by FXMLLoader

    @FXML // fx:id="workDirPathHBox"
    private HBox workDirPathHBox; // Value injected by FXMLLoader

    @FXML // fx:id="workDirPathLabel"
    private Label workDirPathLabel; // Value injected by FXMLLoader

    @FXML // fx:id="workDirPathInput"
    private TextField workDirPathInput; // Value injected by FXMLLoader

    @FXML // fx:id="basePackageHBox"
    private HBox basePackageHBox; // Value injected by FXMLLoader

    @FXML // fx:id="basePackageLabel"
    private Label basePackageLabel; // Value injected by FXMLLoader

    @FXML // fx:id="basePackageInput"
    private TextField basePackageInput; // Value injected by FXMLLoader

    @FXML
    private TextField moduleNameInput;

    @FXML // fx:id="bottomAnchorPane"
    private AnchorPane bottomAnchorPane; // Value injected by FXMLLoader

    @FXML // fx:id="sourceTextBox"
    private VBox sourceTextBox; // Value injected by FXMLLoader

    @FXML // fx:id="genButton"
    private Button genButton; // Value injected by FXMLLoader

    @FXML // fx:id="sourceTextArea"
    private TextArea sourceTextArea; // Value injected by FXMLLoader

    @FXML // fx:id="footerLabel"
    private Label footerLabel; // Value injected by FXMLLoader


    @FXML
    private Tab tableNameGenTab;

    @FXML
    private TextField schemaInput;

    @FXML
    private TextField dbHostInput;

    @FXML
    private TextField dbPortInput;

    @FXML
    private TextField userInput;

    @FXML
    private PasswordField passwordInput;

    @FXML
    private ChoiceBox dataBaseChoice ;

    @FXML
    private TextArea tableNamesTextArea;

    @FXML
    private ChoiceBox<ModelTypeEnum> cboxModelType;

    @FXML
    private Tab ddlGenTab;

    private String sourceText;

    private String tableNamesText;

    private Set<CheckBox> codeLabelCheckBoxs = new HashSet<>();

    private String userInputPackageName = "";

    private String userInputModuleName = "";

    private String userOperationInfoPath = "userOperationInfo.json";


    @FXML
    void onGenButtonClick(ActionEvent event) {
        processSelectedCodeLabels();


        try {
            // 每次生成代码前重新加载配置文件
            springCodeGenService.loadConfig();
            userInputPackageName = userOperationInfo.getBasePackageName();
            if (StringUtils.isNotBlank(userInputPackageName)) {
                springCodeGenService.getParamInfo().setPackageName(userInputPackageName);
            }
            userInputModuleName = userOperationInfo.getModuleName();
            if (StringUtils.isNotBlank(userInputModuleName)) {
                springCodeGenService.getParamInfo().setModuleName(userInputModuleName);
            }
            springCodeGenService.getParamInfo().setDataType("SQL");
            springCodeGenService.getParamInfo().setModelTypeEnum(this.cboxModelType.getValue());

            if (tableNameGenTab.isSelected()) {
                List<String> tableNames = Arrays.stream(tableNamesText.split("[\\s;]")).map(String::trim).collect(Collectors.toList());

                List<String> createTableSqls = new ArrayList<>();
                for (String tableName : tableNames) {
                    if (StringUtils.isNotBlank(tableName)) {
                        List<String> tmpCreateTableSqlList = DataBaseUtils.getCreateTableSql(userOperationInfo.getDataBaseInfo(), tableName);
                        if (tmpCreateTableSqlList.isEmpty()) {
                            UIComponentUtils.createAlertDialog(Alert.AlertType.WARNING, String.format("表名不存在：[%s]", tableName));
                        } else {
                            createTableSqls.addAll(tmpCreateTableSqlList);
                        }
                    }
                }
                String tempSourceText = "";
                if (!createTableSqls.isEmpty()) {
                    tempSourceText = createTableSqls.stream().reduce((a, b) -> a + "\n;" + b).get();
                }
                if (StringUtils.isNotBlank(tempSourceText)) {
                    springCodeGenService.genCode(tempSourceText);
                }
            } else {
                if (StringUtils.isNotBlank(sourceText)) {
                    springCodeGenService.genCode(sourceText);
                }
            }

        } catch (Throwable throwable) {
            //throwable.printStackTrace();
            UIComponentUtils.createAlertDialog(Alert.AlertType.ERROR, "生成代码失败", throwable);
        }
    }

    @FXML
    void onAllCheck(ActionEvent event) {
        for (CheckBox checkBox : codeLabelCheckBoxs) {
            checkBox.setSelected(allCheck.isSelected());
        }
    }

    @FXML
    void onControllerCheck(ActionEvent event) {
        resetAllCheck();
    }

    @FXML
    void onFrontendCheck(ActionEvent event) {
        resetAllCheck();
    }

    @FXML
    void onEntityCheck(ActionEvent event) {
        resetAllCheck();
    }


    @FXML
    void onMapperCheck(ActionEvent event) {
        resetAllCheck();

    }

    @FXML
    void onRepVoCheck(ActionEvent event) {
        resetAllCheck();

    }

    @FXML
    void onReqVoCheck(ActionEvent event) {
        resetAllCheck();

    }

    @FXML
    void onServiceCheck(ActionEvent event) {
        resetAllCheck();
    }

    @FXML
        // This method is called by the FXMLLoader when initialization is complete
    void initialize() {
        assert mainFlowPane != null : "fx:id=\"mainFlowPane\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert topAnchorPane != null : "fx:id=\"topAnchorPane\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert allCheck != null : "fx:id=\"allCheck\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert entityCheck != null : "fx:id=\"entityCheck\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert reqVoCheck != null : "fx:id=\"reqVoCheck\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert repVoCheck != null : "fx:id=\"repVoCheck\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert mapperCheck != null : "fx:id=\"mapperCheck\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert serviceCheck != null : "fx:id=\"serviceCheck\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert controllerCheck != null : "fx:id=\"controllerCheck\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert middleAnchorPane != null : "fx:id=\"middleAnchorPane\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert workDirPathHBox != null : "fx:id=\"workDirPathHBox\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert workDirPathLabel != null : "fx:id=\"workDirPathLabel\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert workDirPathInput != null : "fx:id=\"workDirPathInput\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert bottomAnchorPane != null : "fx:id=\"bottomAnchorPane\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert sourceTextBox != null : "fx:id=\"sourceTextBox\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert genButton != null : "fx:id=\"genButton\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert sourceTextArea != null : "fx:id=\"sourceTextArea\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";
        assert footerLabel != null : "fx:id=\"footerLabel\" was not injected: check your FXML file 'SpringCodeGen.fxml'.";


        try {
            // 加载缓存的用户操作信息
            loadUserOperationInfo();

            // 初始化复选框集合
            initCodeLabelCheckBoxs();

            // 默认全选所有复选框
            allCheck.setSelected(true);
            for (CheckBox checkBox : codeLabelCheckBoxs) {
                checkBox.setSelected(true);
            }

            // 焦点首先落到workDirPathInput上
            workDirPathInput.requestFocus();

            // 组件注入完成以后 初始化springCodeGenService
            springCodeGenService = new SpringCodeGenServiceImpl();

            if (userOperationInfo != null) {
                if (StringUtils.isNotBlank(userOperationInfo.getAppWorkDirPath())) {
                    springCodeGenService.initWorkDirPath(userOperationInfo.getAppWorkDirPath());
                }
            }

            // 监听模块名称变动
            initModuleNameInput();

            // 监听基础包名称变动
            initBasePackageInput();

            // 监听工作目录变动
            initWorkDirPathInput();

            // 监听sourceText变动
            initSourceTextArea();

            // 监听dataBaseChoice变动
            initDataBaseChoice();

            // 监听schemaInput变动
            initSchemaInput();

            initDbHostInput();

            initDbPortInput();

            initDbUserInput();

            initDbPasswordInput();

            initTableNamesTextArea();

            initModelTypeChoice();

        } catch (Throwable throwable) {
            UIComponentUtils.createAlertDialog(Alert.AlertType.ERROR, "生成代码失败", throwable);
        }

    }


    private void initDataBaseChoice() {

        this.dataBaseChoice.setItems(FXCollections.observableArrayList(Arrays.stream(DataBaseEnum.values()).map(DataBaseEnum::getName).toArray()));

        this.dataBaseChoice.setValue(userOperationInfo.getDataBaseInfo().getDataBase());

        this.dataBaseChoice.itemsProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                String dataBase = newValue;
                log.info(String.format("数据库名称设置为:%s", dataBase));
                dataBase = dataBase.trim();
                userOperationInfo.getDataBaseInfo().setDataBase(dataBase);
                saveUserOperationInfo();
            }
        });

    }


    /**
     * 初始化代码生成类型选择
     *
     * @author pengzhikang
     * @date 2023/02/17 14:58
     */
    private void initModelTypeChoice() {

        this.cboxModelType.setItems(FXCollections.observableArrayList(Arrays.stream(ModelTypeEnum.values()).collect(Collectors.toList())));

        ModelTypeEnum modelType = userOperationInfo.getModelTypeEnum() == null ? ModelTypeEnum.MYBATIS : userOperationInfo.getModelTypeEnum();
        this.cboxModelType.setValue(modelType);

        this.cboxModelType.valueProperty().addListener((observable, oldValue, newValue) -> {
            log.info(String.format("模式选择为:%s", newValue));
            userOperationInfo.setModelTypeEnum(newValue);
            saveUserOperationInfo();
        });
    }

    private void initSchemaInput() {

        this.schemaInput.setText(userOperationInfo.getDataBaseInfo().getSchemaName());

        this.schemaInput.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                if (StringUtils.isNotBlank(newValue)) {
                    log.info(String.format("schema名称设置为:%s", newValue));
                    userOperationInfo.getDataBaseInfo().setSchemaName(newValue.trim());
                    saveUserOperationInfo();
                } else {
                    log.info(String.format("schema名称为空"));
                }
            }
        });
    }


    private void initDbHostInput() {

        this.dbHostInput.setText(userOperationInfo.getDataBaseInfo().getHost());

        this.dbHostInput.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                if (StringUtils.isNotBlank(newValue)) {
                    log.info(String.format("数据库Host设置为:%s", newValue));
                    userOperationInfo.getDataBaseInfo().setHost(newValue.trim());
                    saveUserOperationInfo();
                } else {
                    log.info(String.format("数据库Host为空"));
                }
            }
        });
    }

    private void initDbPortInput() {
        if (Objects.nonNull(userOperationInfo.getDataBaseInfo().getPort())) {
            this.dbPortInput.setText(new Integer(userOperationInfo.getDataBaseInfo().getPort()).toString());
        } else {
            this.dbPortInput.setText("");
        }

        this.dbPortInput.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                if (StringUtils.isNotBlank(newValue)) {
                    log.info(String.format("数据库Port设置为:%s", newValue));
                    userOperationInfo.getDataBaseInfo().setPort(Integer.parseInt(newValue.trim()));
                    saveUserOperationInfo();
                } else {
                    log.info(String.format("数据库Port为空"));
                }
            }
        });
    }


    private void initDbUserInput() {
        this.userInput.setText(userOperationInfo.getDataBaseInfo().getUser());
        this.userInput.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                if (StringUtils.isNotBlank(newValue)) {
                    log.info(String.format("数据库用户名设置为:%s", newValue));
                    userOperationInfo.getDataBaseInfo().setUser(newValue);
                    saveUserOperationInfo();
                } else {
                    log.info(String.format("数据库用户名为空"));
                }
            }
        });
    }


    private void initDbPasswordInput() {
        this.passwordInput.setText(userOperationInfo.getDataBaseInfo().getPassword());
        //this.passwordInput.setVisible(true);
        this.passwordInput.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                if (StringUtils.isNotBlank(newValue)) {
                    log.info(String.format("数据库密码设置为:%s", newValue));
                    userOperationInfo.getDataBaseInfo().setPassword(newValue);
                    saveUserOperationInfo();
                } else {
                    log.info(String.format("数据库密码为空"));
                }
            }
        });
    }

    private void initTableNamesTextArea() {
        this.tableNamesTextArea.setPromptText("请输入表名，多个表名用英文分号[;]或换行分隔,然后点击【生成】按钮。");
        tableNamesText = "";
        //this.passwordInput.setVisible(true);
        this.tableNamesTextArea.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                if (StringUtils.isNotBlank(newValue)) {
                    log.info(String.format("表名设置为:%s", newValue));
                    tableNamesText = newValue.trim();
                } else {
                    log.info(String.format("表名设置为空"));
                    tableNamesText = newValue.trim();
                }
            }
        });
    }


    private void initModuleNameInput() {

        this.moduleNameInput.setText(userOperationInfo.getModuleName());

        this.moduleNameInput.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                String moduleName = newValue;
                if (StringUtils.isNotBlank(newValue)) {
                    log.info(String.format("模块名称设置为:%s", moduleName));
                    userInputModuleName = moduleNameInput.getText().trim();
                    userOperationInfo.setModuleName(userInputModuleName);
                    saveUserOperationInfo();
                } else {
                    log.info(String.format("模块名称为空"));
                }
            }
        });
    }


    private void initBasePackageInput() {

        this.basePackageInput.setText(userOperationInfo.getBasePackageName());

        this.basePackageInput.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                // TODO 加包名称校验逻辑
                String basePackage = newValue;

                if (StringUtils.isNotBlank(newValue)) {
                    log.info(String.format("基础包名称设置为:%s", basePackage));
                    userInputPackageName = basePackageInput.getText().trim();
                    userOperationInfo.setBasePackageName(userInputPackageName);
                    saveUserOperationInfo();

                } else {
                    log.info(String.format("基础包名称为空"));
                }
            }
        });
    }


    private void initWorkDirPathInput() {

        this.workDirPathInput.setText(userOperationInfo.getAppWorkDirPath());

        this.workDirPathInput.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                if (FileUtils.dirExists(newValue)) {

                    String path = newValue.replace("\\", "/");

                    log.info(String.format("工作目录设置为:%s", path));
                    // TODO 将工作目录保存到文件中，再次打开程序时直接获取上次的工作目录
                    //  工作目录初始值默认为应用程序安装根目录
                    springCodeGenService.initWorkDirPath(path);
                    userOperationInfo.setAppWorkDirPath(path);
                    saveUserOperationInfo();
                } else {
                    log.info(String.format("目录不存在"));

                }
            }
        });
    }


    private void initSourceTextArea() {
        this.sourceTextArea.setPromptText("请输入CreateTable语句，多个CreateTable语句用英文分号[;]分隔,然后点击【生成】按钮。");
        this.sourceTextArea.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                log.info("sql脚本内容:\n{}", newValue);
                sourceText = newValue;
            }
        });

    }

    private void processSelectedCodeLabels() {
        for (CheckBox checkBox : codeLabelCheckBoxs) {
            if (checkBox.isSelected()) {
                springCodeGenService.addSelectedCodeLabel(checkBox.getText());
                if (checkBox == mapperCheck) {
                    springCodeGenService.addSelectedCodeLabel("mybatis_xml");
                }

                if (checkBox == reqVoCheck) {
                    springCodeGenService.removeSelectedCodeLabel("reqvo");
                    springCodeGenService.addSelectedCodeLabel("add_reqvo");
                    springCodeGenService.addSelectedCodeLabel("update_reqvo");
                    springCodeGenService.addSelectedCodeLabel("qry_reqvo");
                }

                if (checkBox == serviceCheck) {
                    springCodeGenService.addSelectedCodeLabel("service_impl");
                }

            } else {
                springCodeGenService.removeSelectedCodeLabel(checkBox.getText());
                if (checkBox == mapperCheck) {
                    springCodeGenService.removeSelectedCodeLabel("mybatis_xml");
                }
                if (checkBox == serviceCheck) {
                    springCodeGenService.removeSelectedCodeLabel("service_impl");
                }

                if (checkBox == reqVoCheck) {
                    springCodeGenService.removeSelectedCodeLabel("add_reqvo");
                    springCodeGenService.removeSelectedCodeLabel("update_reqvo");
                    springCodeGenService.removeSelectedCodeLabel("qry_reqvo");
                }
            }
        }

        userOperationInfo.setSelectedCodeLabels(springCodeGenService.getSelectedCodeLabels());
        saveUserOperationInfo();
    }

    private void loadUserOperationInfo() {

        try {
            // 加载缓存用户操作信息文件
            File file = new File(userOperationInfoPath);

            if (file.isFile() && file.exists()) {
                // 缓存文件存在时
                InputStream inputStream = new FileInputStream(file);
                userOperationInfo = JSON.parseObject(inputStream, UserOperationInfo.class);
                inputStream.close();
            } else {
                // 缓存不文件存在时
                userOperationInfo = new UserOperationInfo();
                // appWorkDirPath默认取jar文件所在目录
                userOperationInfo.setAppWorkDirPath(new File("").getAbsolutePath());
                // basePackageName默认取项目的groupId
                userOperationInfo.setBasePackageName("com.ysstech.codegen");
                // 默认模块名称为空
                userOperationInfo.setModuleName("");
            }
        } catch (Exception e) {
            e.printStackTrace();
            // log.error(e.getMessage());
        }

    }

    private void saveUserOperationInfo() {

        try {
            OutputStream outputStream = new FileOutputStream(userOperationInfoPath);
            JSON.writeJSONString(outputStream, userOperationInfo, SerializerFeature.PrettyFormat);
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initCodeLabelCheckBoxs() {
        codeLabelCheckBoxs.add(entityCheck);
        codeLabelCheckBoxs.add(reqVoCheck);
        codeLabelCheckBoxs.add(repVoCheck);
        codeLabelCheckBoxs.add(mapperCheck);
        codeLabelCheckBoxs.add(serviceCheck);
        codeLabelCheckBoxs.add(controllerCheck);
    }

    private void resetAllCheck() {
        boolean allSelectedFlag = true;
        for (CheckBox checkBox : codeLabelCheckBoxs) {
            if (!checkBox.isSelected()) {
                allSelectedFlag = false;
                break;
            }
        }
        allCheck.setSelected(allSelectedFlag);
    }


}